#include <GameEngine/Graphics/Lighting/material.h>

#include <GameEngine/Graphics/openGL.h>
#include <GameEngine/Core/Memory/memory_manager.h>


#ifdef GAMEENGINE_NAMESPACES
namespace GameEngine {
#endif

//=====================================================================

Q_DECLARE_OPERATORS_FOR_FLAGS(Material::MaterialParameters)

//=================================================================================

//! Default constructor
Material::Material() : color_(1.0f,1.0f,1.0f), alpha_(1.0f), texture_(nil), name_("") {}

//! Copy constructor
/*!
 @param rhs The material to copy
 */
Material::Material(const Material& rhs) : parameters_(rhs.parameters_),
	acolor_(rhs.acolor_), dcolor_(rhs.dcolor_), scolor_(rhs.scolor_), ecolor_(rhs.ecolor_),
	shininess_(rhs.shininess_), color_(rhs.color_), alpha_(rhs.alpha_), texture_(nil), name_(rhs.name_)
{
	if ( rhs.texture_ != nil ) texture_ = MemoryManager::instance()->allocate(*rhs.texture_);
}

//! Destructor
Material::~Material() {
	MemoryManager::instance()->desallocate(texture_);
}

//! Assignment operator
/*!
 @param rhs The material to copy
 */
Material& Material::operator=(const Material& rhs) {
	if ( &rhs != this ) {
		parameters_ = rhs.parameters_;
		acolor_ = rhs.acolor_;
		dcolor_ = rhs.dcolor_;
		scolor_ = rhs.scolor_;
		ecolor_ = rhs.ecolor_;
		shininess_ = rhs.shininess_;
		color_ = rhs.color_;
		alpha_ = rhs.alpha_;
		name_ = rhs.name_;
		MemoryManager::instance()->desallocate(texture_);
		if ( rhs.texture_ != nil ) texture_ = MemoryManager::instance()->allocate(*rhs.texture_);
		else texture_ = nil;
	}
	return *this;
}

//! Accessor to the ambient color of the material (read only)
/*!
 @return A constant reference to the ambient color of the material
 */
const ColorRGBA& Material::ambient_color() const {
	return acolor_;
}

//! Set the ambient color and activates the ambient parameter of the material
/*!
 @param ambient The new ambient color of the material
 */
void Material::set_ambient_color(const ColorRGBA& ambient) {
	acolor_ = ambient;
	set_active_parameter(AmbientMaterial);
}

//! Accessor to the diffuse color of the material (read only)
/*!
 @return A constant reference to the diffuse color of the material
 */
const ColorRGBA& Material::diffuse_color() const {
	return dcolor_;
}

//! Set the diffuse color and activates the diffuse parameter of the material
/*!
 @param diffuse The new diffuse color of the material
 */
void Material::set_diffuse_color(const ColorRGBA& diffuse) {
	dcolor_ = diffuse;
	set_active_parameter(DiffuseMaterial);
}

//! Accessor to the specular color of the material (read only)
/*!
 @return A constant reference to the specular color of the material
 */
const ColorRGBA& Material::specular_color() const {
	return scolor_;
}

//! Set the specular color and activates the specular parameter of the material
/*!
 @param specular The new specular color of the material
 */
void Material::set_specular_color(const ColorRGBA& specular) {
	scolor_ = specular;
	set_active_parameter(SpecularMaterial);
}

//! Accessor to the emission color of the material (read only)
/*!
 @return A constant reference to the emission color of the material
 */
const ColorRGBA& Material::emission_color() const {
	return ecolor_;
}

//! Set the emission color and activates the emission parameter of the material
/*!
 @param emission The new emission color of the material
 */
void Material::set_emission_color(const ColorRGBA& emission) {
	ecolor_ = emission;
	set_active_parameter(EmissionMaterial);
}

//! Accessor to the shininess of the material (read only)
/*!
 @return The shininess of the material
 */
float Material::shininess() const {
	return shininess_;
}

//! Set the shininess and activates the shininess parameter of the material
/*!
 @param shininess The new shininess of the material
 */
void Material::set_shininess(float shininess) {
	shininess_ = shininess;
	set_active_parameter(ShininessMaterial);
}

//! Accessor to the color of the material (read only)
/*!
 @return The color of the material
 */
const ColorRGB& Material::color() const {
	return color_;
}

//! Set the color and activates the color parameter of the material
/*!
 @param color_rgb The new color of the material
 */
void Material::set_color(const ColorRGB& color_rgb) {
	color_ = color_rgb;
	set_active_parameter(ColorMaterial);
}

//! Accessor to the transparency of the material (read only)
/*!
 @return The transparency of the material
 */
float Material::alpha() const {
	return alpha_;
}

//! Set the transparency and activates the transparency parameter of the material
/*!
 @param alpha The new transparency of the material
 */
void Material::set_alpha(float alpha) {
	alpha_ = alpha;
	set_active_parameter(AlphaMaterial);
}

//! Accessor to the texture of the material (read only)
/*!
 @return The texture of the material (may be nil if texture is undefined)
 */
Texture2d* Material::texture() const {
	return texture_;
}

//! Set the texture and activates the texture parameter of the material
/*!
 @param texture The new texture of the material
 */
void Material::set_texture(const Texture2d* texture) {
	if ( texture == nil ) {
		MemoryManager::instance()->desallocate(texture_);
		texture_ = nil;
		set_active_parameter(TexturedMaterial, false);
		return;
	}

	if ( texture_ != nil ) *texture_ = *texture;
	else texture_ = MemoryManager::instance()->allocate<Texture2d>(*texture);
	set_active_parameter(TexturedMaterial);
}

//! Accessor to the name of the material (read only)
/*!
 @return A constant reference to the name of the material
 */
const GEString& Material::name() const {
	return name_;
}

//! Accessor to the name of the material (read-write)
/*!
 @return A reference to the name of the material
 */
GEString& Material::name() {
	return name_;
}

//! Test if the material parameter is active or not
/*!
 @param parameter The material parameter to test
 @return True if the material parameter is active, false otherwise
 */
bool Material::active_parameter(MaterialParameter parameter) const {
	return parameters_.testFlag(parameter);
}

//! Set the material parameter active or inactive
/*!
 @param parameter The material parameter to activate / desactivate
 @param enable If set to true, the material parameter should be activated. Otherwise,
               it should be desactivated.
 @return True if the parameter could be activated / desactivated, false otherwise
 */
bool Material::set_active_parameter(MaterialParameter parameter, bool enable) {
	// Check if parameter is already activated / desactivated
	if ( active_parameter(parameter) == enable ) return false;

	// Activate / desactivate parameter
	parameters_ ^= parameter;
	return true;
}

//! Apply the material parameters
void Material::apply_material() const {
	GLenum face = GL_FRONT_AND_BACK;

	// Setup the ambient color array
	if ( parameters_.testFlag(AmbientMaterial) ) {
		float ambient_color[4] = { acolor_.r(), acolor_.g(), acolor_.b(), acolor_.a() };
		glMaterialfv(face, GL_AMBIENT, ambient_color);
	}

	// Setup the diffuse color array
	if ( parameters_.testFlag(DiffuseMaterial) ) {
		float diffuse_color[4] = { dcolor_.r(), dcolor_.g(), dcolor_.b(), dcolor_.a() };
		glMaterialfv(face, GL_DIFFUSE, diffuse_color);
	}

	// Setup the specular color array
	if ( parameters_.testFlag(SpecularMaterial) ) {
		float specular_color[4] = { scolor_.r(), scolor_.g(), scolor_.b(), scolor_.a() };
		glMaterialfv(face, GL_SPECULAR, specular_color);
	}

	// Setup the emission color array
	if ( parameters_.testFlag(EmissionMaterial) ) {
		float emission_color[4] = { ecolor_.r(), ecolor_.g(), ecolor_.b(), ecolor_.a() };
		glMaterialfv(face, GL_EMISSION, emission_color);
	}

	// Setup the shininess
	if ( parameters_.testFlag(ShininessMaterial) ) {
		glMaterialf(face, GL_SHININESS, shininess_);
	}

	// Setup the transparency
	bool ctest = parameters_.testFlag(ColorMaterial);
	bool atest = parameters_.testFlag(AlphaMaterial);
	if ( ctest || atest ) {
		float r = 1.0f, g = 1.0f, b = 1.0f, a = 1.0f;
		if ( ctest ) {
			r = color_.r();
			g = color_.g();
			b = color_.b();
		}
		if ( atest ) a = alpha_;
		glColor4f(r,g,b,a);
	}

	// Setup the texture
	if ( parameters_.testFlag(TexturedMaterial) ) {
		if ( texture_ != nil ) {
			glEnable(GL_TEXTURE_2D);
			texture_->bind_texture();
		}
	}
}

//! Remove the material parameters
void Material::remove_material() const {
	// Setup the texture
	if ( parameters_.testFlag(TexturedMaterial) ) {
		if ( texture_ != nil ) {
			texture_->unbind_texture();
		}
	}
}

//=================================================================================

#ifdef GAMEENGINE_NAMESPACES
} // namespace GameEngine
#endif
